﻿using Microsoft.Crm.Services.Utility;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Metadata;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel.Description;
using System.Text;
using System.Threading.Tasks;

namespace CrmField.SolutionEntitiesFilter
{
    /// <summary>
    /// Extension of crmsvcutil.exe, which filters the entities to be generated, taking only the ones in 
    /// the current solution that is being used.
    /// Solution name must be first part of namespace, splitting by dotes.
    /// Example: MySolutionName.CommonLibraray => MySolutionName is the solution name we are trying to get
    /// 
    /// See another good example of crmsvcutil.exe from Erick Pool
    /// http://erikpool.blogspot.co.uk/2011/03/filtering-generated-entities-with.html
    /// 
    /// </summary>
    public class CodeWriterFilter : ICodeWriterFilterService
    {
        //list of entity names to generate classes for.
        private HashSet<string> _validEntities = new HashSet<string>();

        //reference to the default service.
        private ICodeWriterFilterService _defaultService = null;

        private IOrganizationMetadata _organisationMetadata;

        /// <summary>
        /// Solution passed as init parameter
        /// </summary>
        private string _solutionName = string.Empty;

        /// <summary>
        /// url passed as init parameter
        /// </summary>
        private string _url = string.Empty;

        /// <summary>
        /// user passed as init parameter
        /// </summary>
        private string _user = string.Empty;

        /// <summary>
        /// password passed as init parameter
        /// </summary>
        string _password = string.Empty;

        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="defaultService">default implementation</param>
        public CodeWriterFilter(ICodeWriterFilterService defaultService, IDictionary<string, string> parameters)
        {
            this._defaultService = defaultService;

            if (parameters != null && parameters.Keys != null)
            {
                if (parameters.Keys.Contains("namespace"))
                    _solutionName = GetSolutionNameFromNamespace(parameters["namespace"]);
                if (parameters.Keys.Contains("url"))
                    _url = parameters["url"];
                if (parameters.Keys.Contains("username"))
                    _user = parameters["username"];
                if (parameters.Keys.Contains("password"))
                    _password = parameters["password"];
            }
     
        }

        private void LoadEntitiesInSolution()
        {
            if (_solutionName != string.Empty && _url != string.Empty && _user!=string.Empty && _password!=string.Empty)
            {
                ClientCredentials clientCrendentials = new ClientCredentials();
                clientCrendentials.UserName.UserName = _user;
                clientCrendentials.UserName.Password = _password;

                using (OrganizationServiceProxy organizationProxy = new OrganizationServiceProxy(new Uri(_url), null, clientCrendentials, null))
                {
                    QueryExpression solutionQuery = new QueryExpression("solution");
                    solutionQuery.Criteria.AddCondition("uniquename", ConditionOperator.Equal, _solutionName);

                    EntityCollection entityCollection = organizationProxy.RetrieveMultiple(solutionQuery);
                    if (entityCollection.Entities.Count > 0)
                    {
                        Trace.TraceInformation("Solution {0} has been found", _solutionName);
                        Entity solution = entityCollection.Entities[0];
                        QueryExpression solutionEntitiesQuery = new QueryExpression("solutioncomponent");
                        solutionEntitiesQuery.ColumnSet.AddColumn("objectid");
                        solutionEntitiesQuery.Criteria.AddCondition("solutionid", ConditionOperator.Equal, solution["solutionid"]);
                        solutionEntitiesQuery.Criteria.AddCondition("componenttype", ConditionOperator.Equal, 1); //1 = Entity

                        EntityCollection entitiesCollection = organizationProxy.RetrieveMultiple(solutionEntitiesQuery);
                        Guid entityId = Guid.Empty;
                        EntityMetadata entityMedata = null;
                        foreach (Entity entity in entitiesCollection.Entities)
                        {
                            entityId = (Guid)entity["objectid"];
                            entityMedata = this._organisationMetadata.Entities.SingleOrDefault(e => e.MetadataId == entityId);
                            if (entityMedata != null)
                                this._validEntities.Add(entityMedata.LogicalName.ToLowerInvariant());
                        }
                    }
                    else
                        Trace.TraceInformation("There is not any solution with name {0}. Url: {1} ; user: {2} ; password: {3}", _solutionName, _url, _user, _password);
                }

            }
        }

        /// <summary>
        /// Solution name must be first part of namespace, splitting by dotes.
        /// Example: MySolutionName.CommonLibraray => MySolutionName is the solution name we are trying to get
        /// </summary>
        /// <param name="?"></param>
        /// <returns></returns>
        private string GetSolutionNameFromNamespace(string _namespace)
        {
            string[] _namespaceInArray = _namespace.Split('.');
            return _namespaceInArray[0];
        }

        /// <summary>
        /// /Use filter entity list to determine if the entity class should be generated.
        /// </summary>
        public bool GenerateEntity(EntityMetadata entityMetadata, IServiceProvider services)
        {
            if (this._organisationMetadata == null)
            {
                IMetadataProviderService service = services.GetService(typeof(IMetadataProviderService)) as IMetadataProviderService;
                this._organisationMetadata = service.LoadMetadata();
                LoadEntitiesInSolution();
            }

            return (_validEntities.Contains(entityMetadata.LogicalName.ToLowerInvariant()));
        }

        //All other methods just use default implementation:

        public bool GenerateAttribute(AttributeMetadata attributeMetadata, IServiceProvider services)
        {
            return _defaultService.GenerateAttribute(attributeMetadata, services);
        }

        public bool GenerateOption(OptionMetadata optionMetadata, IServiceProvider services)
        {
            return _defaultService.GenerateOption(optionMetadata, services);
        }

        public bool GenerateOptionSet(OptionSetMetadataBase optionSetMetadata, IServiceProvider services)
        {
            return _defaultService.GenerateOptionSet(optionSetMetadata, services);
        }

        public bool GenerateRelationship(RelationshipMetadataBase relationshipMetadata, EntityMetadata otherEntityMetadata, IServiceProvider services)
        {
            return _defaultService.GenerateRelationship(relationshipMetadata, otherEntityMetadata, services);
        }

        public bool GenerateServiceContext(IServiceProvider services)
        {
            return _defaultService.GenerateServiceContext(services);
        }
    }
}
